package com.lj.common_web.utils;

import cn.hutool.core.net.Ipv4Util;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;
import java.io.OutputStream;
import java.util.StringJoiner;
import java.util.function.Consumer;

/**
 * @author luojing
 * @version 1.0
 * @date 2023/3/22 9:22
 * <p>
 * HttpServlet工具类 可以获取当前请求的HttpServletRequest和HttpServletResponse对象
 */
public class ServletUtil {

    /**
     * 回环地址，等效于127.0.0.1
     */
    private static final String LOOPBACK_IP = "0:0:0:0:0:0:0:1";

    /**
     * 从请求体中获取参数
     *
     * @param paramName 参数名称
     * @return 获取到的参数值
     */
    public static String getParamFromRequest(String paramName) {
        HttpServletRequest request = getRequest();
        return getParamFromRequest(request, paramName);
    }

    /**
     * 从请求体中获取参数
     *
     * @param request   请求体
     * @param paramName 参数名称
     * @return 获取到的参数值
     */
    public static String getParamFromRequest(HttpServletRequest request, String paramName) {
        // 1. 尝试从请求体里面读取
        String paramValue = request.getParameter(paramName);

        // 2. 尝试从header里读取
        if (ObjectUtil.isEmpty(paramValue)) {
            paramValue = request.getHeader(paramName);
        }
        // 3. 尝试从cookie里读取
        if (ObjectUtil.isEmpty(paramValue)) {
            Cookie[] cookies = request.getCookies();
            if (ObjectUtil.isNotEmpty(cookies)) {
                for (Cookie cookie : cookies) {
                    String cookieName = cookie.getName();
                    if (cookieName.equals(paramName)) {
                        return cookie.getValue();
                    }
                }
            }
        }
        // 4. 返回
        return paramValue;
    }

    /**
     * {@link cn.hutool.extra.servlet.ServletUtil#getClientIP}
     * 从这里搬运过来的，hutool目前的版本只支持jdk8,HttpServletRequest还是用的javax的
     */
    public static String getClientIP(HttpServletRequest request, String... otherHeaderNames) {
        String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
        if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
            headers = ArrayUtil.addAll(headers, otherHeaderNames);
        }
        String clientIP = getClientIPByHeader(request, headers);
        return LOOPBACK_IP.equals(clientIP) ? Ipv4Util.LOCAL_IP : clientIP;
    }


    /**
     * {@link cn.hutool.extra.servlet.ServletUtil#getClientIPByHeader}
     * 从这里搬运过来的，hutool目前的版本只支持jdk8,HttpServletRequest还是用的javax的
     */
    public static String getClientIPByHeader(HttpServletRequest request, String... headerNames) {
        String ip;
        for (String header : headerNames) {
            ip = request.getHeader(header);
            if (!NetUtil.isUnknown(ip)) {
                return NetUtil.getMultistageReverseProxyIp(ip);
            }
        }
        ip = request.getRemoteAddr();
        return NetUtil.getMultistageReverseProxyIp(ip);
    }

    /**
     * 获取当前请求的HttpServletRequest对象
     *
     * @return 当前请求的HttpServletRequest对象
     */
    public static HttpServletRequest getRequest() {
        ServletRequestAttributes servletRequestAttributes = getRequestAttributes();
        return servletRequestAttributes.getRequest();
    }

    /**
     * 获取当前请求的HttpServletResponse对象
     *
     * @return 当前请求的HttpServletResponse对象
     */
    public static HttpServletResponse getResponse() {
        ServletRequestAttributes servletRequestAttributes = getRequestAttributes();
        return servletRequestAttributes.getResponse();
    }

    /**
     * 获取当前请求的RequestAttributes对象
     *
     * @return 当前请求的RequestAttributes对象
     */
    public static ServletRequestAttributes getRequestAttributes() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            throw new UnsupportedOperationException();
        }
        return (ServletRequestAttributes) requestAttributes;
    }

    /**
     * 下载文件，向响应流中写入数据
     *
     * @param fileName 文件名称(会根据文件后缀名确认响应头)
     * @param consumer 响应输出流 -> 写入逻辑
     * @throws IOException 可能会出现网络io等异常
     */
    public static void download(String fileName, Consumer<OutputStream> consumer) throws IOException {
        HttpServletResponse response = getResponse();
        try {
            // 设置响应头
            setFileResponseHeader(response, fileName, true, -1);
            // 执行下载写入响应流逻辑
            consumer.accept(response.getOutputStream());
        } catch (Exception e) {
            // 发生了错误重置响应体，抛出异常由全局异常处理来解决
            response.reset();
            throw e;
        }
    }

    /**
     * 设置文件流的响应头
     *
     * @param fileName 文件名称，带后缀名哦！
     */
    public static void setFileResponseHeader(String fileName) {
        setFileResponseHeader(fileName, true);
    }

    /**
     * 设置文件流的响应头
     *
     * @param fileName 文件名称，带后缀名哦！
     * @param download 是否要进行下载(触发浏览器自动下载)
     */
    public static void setFileResponseHeader(String fileName, boolean download) {
        setFileResponseHeader(getResponse(), fileName, download, -1);
    }

    /**
     * 设置文件流的响应头
     *
     * @param fileName 文件名称，带后缀名哦！
     * @param download 是否要进行下载(触发浏览器自动下载)
     * @param size     文件的大小
     */
    public static void setFileResponseHeader(HttpServletResponse response, String fileName, boolean download, int size) {
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType(HttpUtil.getMimeType(fileName, MediaType.APPLICATION_OCTET_STREAM_VALUE));
        StringJoiner expose = new StringJoiner(",");
        fileName = URLUtil.encode(fileName);
        if (download) {
            // 响应，告诉前端，这是一个附件，需要进行下载 文件名:filename
            expose.add("Content-Disposition");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
        }
        expose.add("filename");
        // 这是为了满足前端下载，因为如果ContentType不是application/octet-stream的话，就没有了文件名称
        response.setHeader("filename", fileName);
        // 本地测不出来这个问题,线上会获取不到Content-Disposition参数,需要暴露出来
        response.setHeader("Access-Control-Expose-Headers", expose.toString());
        if (size > 0) {
            response.addHeader("Content-Length", NumberUtil.toStr(size));
        }
    }

    /**
     * 往响应体中写入json数据
     *
     * @param msg 要写入的对象，会自动进行json序列化
     */
    public static void writeJson(HttpServletResponse response, Object msg) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding(getRequest().getCharacterEncoding());
        response.getWriter().write(JSONUtil.toJsonStr(msg));
    }
}
